/**
 * 
 */
package server;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author Fleischhacker
 *
 */
public class ServerThread implements Runnable{
	
	/**
	 * Active TCP Connection with the Client
	 */
	private Socket sock;
	/**
	 * Active UDP Connection with the Client
	 */
	private DatagramSocket dsock;
	/**
	 * UDP Port of the connected client
	 */
	private int pUDP;
	
	private AuctionManager auctions;
	private UserManager users;
	/**
	 * Active User for this thread
	 */
	private User user = null;
	/**
	 * Flag if the Thread is still active
	 */
	private boolean active;
	
	private BufferedReader tcpIN;
	private BufferedWriter tcpOUT;
	
	public ServerThread(Socket _sock, AuctionManager _auctions, UserManager _users, User _user, int _pUDP){
		this.sock = _sock;
		this.auctions = _auctions;
		this.users = _users;
		this.user = _user;
		this.pUDP = _pUDP;
		active = false;
	}
	
	public void run(){
		System.out.println("= \n= Client Connected: "+sock.getInetAddress());
		System.out.println("= + Client's UDP Port : "+pUDP);
		System.out.println("= + Client's current User: "+user.getScreenname());
		String message = null;
		active = true;
		try{
			tcpIN = new BufferedReader(new InputStreamReader(sock.getInputStream()));
			tcpOUT = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
			dsock = new DatagramSocket();
			//Send the User the log-in message
			sendTCPMessage("Successfully logged in as "+user.getScreenname());
			//First, if the User has old Messages, send them!
			sendNotification();
			//Set the User Online
			user.goOnline();
			message = null;
			//Wait for incoming requests
			while((message = tcpIN.readLine())!=null){
				if(message.startsWith("!end")){//Logout Sequence started
					if(hasActiveUser()){
						user.goOffline();
						user = null;
					}
					sendTCPMessage("#stop");
					break;
				}
				else if(message.startsWith("!login")){
					if(user != null)
						sendTCPMessage("You are already logged in!");
					else{						
						String screenname = message.split(" ")[1];
						user = users.getUserByScreenname(screenname);
						if(user == null){
							user = new User(screenname);
							users.addUser(user);
						}
						if(user.isOnline()){ //ERR: User already online!
							sendTCPMessage("User '"+user.getScreenname()+"' is already online!");
							user = null;
						}
						else{
							user.goOnline();
							sendNotification();
							sendTCPMessage("Successfully logged in as "+user.getScreenname());
						}
					}
				}
				else if(message.startsWith("!logout")){ //exat 0 parammeters: !logout
					if(hasActiveUser()){
						sendTCPMessage("Successfully logged out as "+user.getScreenname());
						user.goOffline();
						user = null;
					}
					//sendTCPMessage("#resock");
					//break;
				}
				else if(message.startsWith("!list")){
					if(hasActiveUser()){
						sendTCPMessage("[ID] ~ [Description] ~ [End Date] ~ [Owner] ~ [H. Bid] ~ [H. Bid User]");
						for(Auction a : auctions.listAuctions()){
							if(a.isActive()){
								sendTCPMessage(a.toString());//Auction.toString parsed for the output format
							}
						}
					}
						
				}
				else if(message.startsWith("!create")){ //minimum 2 Parameter: !create <duration> <description>
					if(hasActiveUser()){
						String descr = "";
						for(int i = 2; i < message.split(" ").length ;i++){
							descr += message.split(" ")[i]+" ";
						}
						Auction a = auctions.createAuction(descr,user,Integer.parseInt(message.split(" ")[1]));
						sendTCPMessage("An Auction '"+a.getDescription()+"' with id '"+a.getID()+"' has been created and will end on '"+a.getEndDateString()+"'.");
					}
				}
				else if(message.startsWith("!bid")){ //exact 2 parameters: !bid <auction-id> <amount>
					if(hasActiveUser()){
						int aid = Integer.parseInt(message.split(" ")[1]);
						double amount = Double.parseDouble(message.split(" ")[2]);
						Auction a = auctions.getAuctionById(aid);
						if(a == null)
							sendTCPMessage("The Auction with ID '"+aid+"' doesn't exist (anymore)!");
						else{
							if(a.isActive()){
								boolean suc = false;
								User oldbidder;
								synchronized(a){
									oldbidder = a.getCurrentTopBidder(); //Null if no current bid exists
									suc = a.overBid(user, amount);
								}
								if(suc){ //Successfull bid
									if(oldbidder != null)
										oldbidder.addNotification("!new-bid "+a.getDescription());
									sendTCPMessage("You successfully bid with '"+a.getBidString()+"' on '"+a.getDescription()+"'.");
								}
								else{ //Unsuccessfull bid
									sendTCPMessage("You unsuccessfully bid on '"+a.getDescription()+"'.");
								}
							}
							else
								sendTCPMessage("The Auction with ID '"+aid+"' doesn't exist (anymore)!");
						}
					}
				}
			}
		}catch(IOException ioe){//Disconnected by Client - handling
			//System.out.println("IOException in ServerThread.run()");
			//ioe.printStackTrace();
		}catch(Exception e){
			e.printStackTrace();
		}
		finally{
			active = false;
			if(user != null)
				user.goOffline();
			user = null;
			try{ //Closing in- and outputstreams
				if(tcpIN != null)
					tcpIN.close();
				if(tcpOUT != null)
					tcpOUT.close();
				if(dsock != null)
					dsock.close();
				if(sock != null)
					sock.close();
			}catch(IOException ioe){
				ioe.printStackTrace();
				System.out.println("ERROR @ServerThread.run():"+ioe.getMessage());
			}
		}
	}
	
	/**
	 * Checks UDP Notificiations for the User and sends them if there are any
	 * @throws IOException 
	 */
	public void sendNotification() throws IOException{
		String message;
		while((message = user.getNotification()) != null){
			sendUDPMessage(message);
		}
	}
	
	/**
	 * Checks if the user is logged in or out!
	 * @return
	 * @throws IOException 
	 */
	private boolean hasActiveUser() throws IOException{
		if(user == null){
			sendTCPMessage("You have to log in first!");
			return false;
		}
		return true;
	}
	
	/**
	 * Returns User-reference (used for Notification Handling)
	 * @return
	 */
	public User getUserReference(){
		return this.user;
	}
	
	/**
	 * Sends an UDP-Message to the connected Client!
	 * @param _msg
	 * @throws IOException 
	 */
	private void sendUDPMessage(String _msg) throws IOException{
		if(dsock != null && !dsock.isClosed()){
			byte[] buffer = new byte[256];
			buffer = _msg.getBytes();
			InetAddress address = sock.getInetAddress();
			DatagramPacket dpack = new DatagramPacket(buffer,buffer.length,address,pUDP);
			dsock.send(dpack);
		}
		else
			throw new IOException("Socket already closed!");
	}
	
	/**
	 * Sends an TCP-Message to the connected Client!(ClientTCPThread is the Listener)
	 * @param _msg
	 * @throws IOException 
	 */
	private void sendTCPMessage(String _msg) throws IOException{
		if(sock != null && !sock.isClosed()){
			tcpOUT.write(_msg);
			tcpOUT.newLine();
			tcpOUT.flush();
		}
	}
	
	/**
	 * Shutdown ServerThread
	 * 	Shutdown Order:
	 * 		1. If the connection is still active => send '#stop' to the Client
	 * 		2. Close Streams
	 */
	public void shutdown(){
		active = false;
		try {
			if(tcpOUT != null && sock != null && !sock.isClosed()){//at this point, user CAN'T be null!
				if(user != null)
					System.out.println("= Sending Logout to User '"+user.getScreenname()+"' Client!");
				else
					System.out.println("= Sending Logout to Anonymous Client!");
				sendTCPMessage("#stop"); //Shutdown command to client!
			}
			if(tcpOUT != null)
				tcpOUT.close();
			if(tcpIN != null)
				tcpIN.close();
			if(sock != null)
				sock.close();
		} catch (IOException e) {//e.printStackTrace();
			System.out.println("ERROR @ServerThread.shutdown():"+e.getMessage());
		}
	}
	
	public boolean isActive(){
		return active;
	}

}
